package flow

import (
	"fmt"
	"http"
	"strconv"
)

type Context interface {
	URL() string
	Next() (http.ResponseWriter, *http.Request)
	Close()
}

type Flow func(Context)

type Mux struct {
	path string
	context map[string]*flowContext
}

func NewMux(path string) *Mux {
	return &Mux{path, make(map[string]*flowContext)}
}

var flowId = make(chan int)

func init() {
	go func() {
		for n := 0; ; n++ {
			flowId <- n
		}
	}()
}

func (mux *Mux) newContext() *flowContext {
	id := strconv.Itoa(<-flowId)
	c := newFlowContext(id, fmt.Sprintf("%s?flowId=%s", mux.path, id))
	mux.context[id] = c
	return c
}

func (mux *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	id := r.FormValue("flowId")
	c, ok := mux.context[id]
	if !ok {
		http.Error(w, "unknown flow", http.StatusBadRequest)
		return
	}
	mux.handle(c, w, r)
}

func (mux *Mux) Become(f Flow, w http.ResponseWriter, r *http.Request) {
	c := mux.newContext()
	go f(c)
	mux.handle(c, w, r)
}

func (mux *Mux) handle(c *flowContext, w http.ResponseWriter, r *http.Request) {
	done := make(chan bool)
	c.req <- &httpRequest{w, r, done}
	if <-done {
		// shut down
		mux.context[c.id] = nil, false
	}
}

type httpRequest struct {
	resp http.ResponseWriter
	req  *http.Request
	done chan bool
}

func newFlowContext(id, url string) *flowContext {
	return &flowContext{id: id, url: url, req: make(chan *httpRequest)}
}

type flowContext struct {
	id  string
	url string
	req chan *httpRequest
	cur *httpRequest
}

func (c *flowContext) URL() string {
	return c.url
}

func (c *flowContext) Next() (http.ResponseWriter, *http.Request) {
	c.done(false)
	c.cur = <-c.req
	return c.cur.resp, c.cur.req
}

func (c *flowContext) Close() {
	c.done(true)
}

func (c *flowContext) done(shutdown bool) {
	if c.cur != nil {
		c.cur.done <- shutdown
		c.cur = nil
	}
}
